package com.ruiysoft.pub.aop;

import com.alibaba.fastjson.JSON;
import com.ruiysoft.exception.BizException;
import com.ruiysoft.pub.anno.DuplicateLock;
import com.ruiysoft.util.Md5Util;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * Date : 2019/1/16
 *
 * @author WenJialu
 */
@Aspect
@Component
@Slf4j
public class DuplicateLockAspect {

	@Resource
	RedisTemplate<String, String> redisTemplate;

	private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
	private static final String LOCK = "LOCKED";

	@Pointcut("@annotation(com.ruiysoft.pub.anno.DuplicateLock)")
	public void pointCut() {
	}

	@Around("pointCut()")
	@Order(2)
	public Object duplicateLock(ProceedingJoinPoint point) throws Throwable {
		MethodSignature signature = (MethodSignature) point.getSignature();

		Method method = signature.getMethod();
		String[] paramNames = signature.getParameterNames();
		Object[] args = point.getArgs();

		DuplicateLock anno = method.getAnnotation(DuplicateLock.class);
		String key = anno.key();
		String userId = anno.userId();
		long expire = anno.expire();
		TimeUnit timeUnit = anno.timeUnit();

		EvaluationContext context = new StandardEvaluationContext();
		for (int i = 0; i < paramNames.length; i++) {
			context.setVariable(paramNames[i], args[i]);
		}

		key = key.startsWith("#")
				      ? Optional.ofNullable(EXPRESSION_PARSER.parseExpression(key).getValue(context))
						        .map(v -> Md5Util.MD5(JSON.toJSONString(v)))
						        .orElse(key)
				      : key;
		userId = userId.startsWith("#")
				         ? Optional.ofNullable(EXPRESSION_PARSER.parseExpression(userId).getValue(context, String.class))
						           .orElse(userId)
				         : userId;

		String cacheKey = userId + key;
		Boolean set = redisTemplate.opsForValue().setIfAbsent(cacheKey, LOCK);
		if (null != set && set) {
			redisTemplate.expire(cacheKey, expire, timeUnit);
//			try {
			//异常向上抛出
			return point.proceed(args);
//			} catch (Throwable throwable) {
//				log.error("PointCut of Duplicate Lock execute Failed，Method:" + method.getName(), throwable);
//				throw new BizException("请求失败，请稍后再试");
//			}
		} else {
			throw new BizException("请勿重复提交");
		}
	}
}
